﻿using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using VisionCloud.Utility;

namespace VisionCloud.Web.Middleware.ProxyRewrite
{
    public class FileContentType
    {
        private static IDictionary<string, string> _mappings = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase) { { ".pdf", "application/pdf" }, { ".json", "application/json" } };

        public static string GetMimeType(string extension)
        {
            if (extension == null)
            {
                throw new ArgumentNullException("extension");
            }

            if (!extension.StartsWith("."))
            {
                extension = "." + extension;
            }

            string mime;

            return _mappings.TryGetValue(extension, out mime) ? mime : "application/octet-stream";
        }
    }

    public class ProxyMiddleware
    {
        private const string CDN_HEADER_NAME = "Cache-Control";
        private static readonly string[] NotForwardedHttpHeaders = new[] { "Connection", "Host" };
        private readonly RequestDelegate _next;
        private readonly ILogger<ProxyMiddleware> _logger;

        public ProxyMiddleware(
               RequestDelegate next,
               ILogger<ProxyMiddleware> logger

               )
        {
            _next = next;
            _logger = logger;
        }

        /// <summary>
        /// 通过中间件,拦截访问,检测前缀,并转发
        /// </summary>
        /// <param name="context"></param>
        /// <param name="urlRewriter"></param>
        /// <returns></returns>
        public async Task Invoke(HttpContext context, IUrlRewriter urlRewriter, ProxyHttpClient proxyHttpClient)
        {
            bool validateUri = false;
            foreach (var item in VisionCloudGlobalContext.MinioPrefix)
            {
                // 是否需要重定向
                if (context.Request.Path.StartsWithSegments($"/{item.Key}"))
                {
                    validateUri = true;
                    break;
                }
            }
            if (validateUri)
            {
                FileInfo fi = new FileInfo(context.Request.Path);
                var contentType = FileContentType.GetMimeType(fi.Extension);
                context.Request.ContentType = contentType;

                var targetUri = await urlRewriter.RewriteUri(context);
                if (targetUri != null)
                {
                    var requestMessage = GenerateProxifiedRequest(context, targetUri);
                    await GetAsync(context, requestMessage, proxyHttpClient);
                    return;
                }
            }

            await _next(context);
        }

        private async Task GetAsync(HttpContext context, HttpRequestMessage requestMessage, ProxyHttpClient proxyHttpClient)
        {
            using (var responseMessage = await proxyHttpClient.Client.GetAsync(requestMessage.RequestUri, context.RequestAborted))
            {
                context.Response.StatusCode = (int)responseMessage.StatusCode;
                foreach (var header in responseMessage.Headers)
                {
                    context.Response.Headers[header.Key] = header.Value.ToArray();
                }
                foreach (var header in responseMessage.Content.Headers)
                {
                    context.Response.Headers[header.Key] = header.Value.ToArray();
                }
                context.Response.Headers.Remove("transfer-encoding");
                if (!context.Response.Headers.ContainsKey(CDN_HEADER_NAME))
                {
                    context.Response.Headers.Add(CDN_HEADER_NAME, "no-cache, no-store");
                }
                // 请求文件返回类型，未识别：application/octet-stream
                context.Response.ContentType = context.Request.ContentType;
                await responseMessage.Content.CopyToAsync(context.Response.Body);
            }
        }

        private async Task SendAsync(HttpContext context, HttpRequestMessage requestMessage, ProxyHttpClient proxyHttpClient)
        {
            using (var responseMessage = await proxyHttpClient.Client.GetAsync(requestMessage.RequestUri, context.RequestAborted))
            //  using (var responseMessage = await proxyHttpClient.Client.SendAsync(requestMessage, HttpCompletionOption.ResponseContentRead, context.RequestAborted))
            {
                context.Response.StatusCode = (int)responseMessage.StatusCode;

                foreach (var header in responseMessage.Headers)
                {
                    context.Response.Headers[header.Key] = header.Value.ToArray();
                }

                foreach (var header in responseMessage.Content.Headers)
                {
                    context.Response.Headers[header.Key] = header.Value.ToArray();
                }

                context.Response.Headers.Remove("transfer-encoding");

                if (!context.Response.Headers.ContainsKey(CDN_HEADER_NAME))
                {
                    context.Response.Headers.Add(CDN_HEADER_NAME, "no-cache, no-store");
                }

                await responseMessage.Content.CopyToAsync(context.Response.Body);
            }
        }

        private static HttpRequestMessage GenerateProxifiedRequest(HttpContext context, Uri targetUri)
        {
            var requestMessage = new HttpRequestMessage();
            CopyRequestContentAndHeaders(context, requestMessage);
            requestMessage.RequestUri = targetUri;
            requestMessage.Headers.Host = targetUri.Host;
            requestMessage.Method = GetMethod(context.Request.Method);
            return requestMessage;
        }

        private static void CopyRequestContentAndHeaders(HttpContext context, HttpRequestMessage requestMessage)
        {
            var requestMethod = context.Request.Method;
            if (!HttpMethods.IsGet(requestMethod) &&
                !HttpMethods.IsHead(requestMethod) &&
                !HttpMethods.IsDelete(requestMethod) &&
                !HttpMethods.IsTrace(requestMethod))
            {
                var streamContent = new StreamContent(context.Request.Body);
                requestMessage.Content = streamContent;
            }

            foreach (var header in context.Request.Headers)
            {
                if (!NotForwardedHttpHeaders.Contains(header.Key))
                {
                    if (header.Key != "User-Agent")
                    {
                        if (!requestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray()) && requestMessage.Content != null)
                        {
                            requestMessage.Content?.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray());
                        }
                    }
                    else
                    {
                        string userAgent = header.Value.Count > 0 ? (header.Value[0] + " " + context.TraceIdentifier) : string.Empty;

                        if (!requestMessage.Headers.TryAddWithoutValidation(header.Key, userAgent) && requestMessage.Content != null)
                        {
                            requestMessage.Content?.Headers.TryAddWithoutValidation(header.Key, userAgent);
                        }
                    }
                }
            }
        }

        private static HttpMethod GetMethod(string method)
        {
            if (HttpMethods.IsDelete(method)) return HttpMethod.Delete;
            if (HttpMethods.IsGet(method)) return HttpMethod.Get;
            if (HttpMethods.IsHead(method)) return HttpMethod.Head;
            if (HttpMethods.IsOptions(method)) return HttpMethod.Options;
            if (HttpMethods.IsPost(method)) return HttpMethod.Post;
            if (HttpMethods.IsPut(method)) return HttpMethod.Put;
            if (HttpMethods.IsTrace(method)) return HttpMethod.Trace;
            return new HttpMethod(method);
        }
    }
}